# -*- coding: utf-8 -*-
import datetime
import decimal
import json
import string

import random
from http.client import HTTPException

from flask import make_response, current_app
from flask_restful import reqparse, Api
from sqlalchemy.orm import class_mapper

parse = reqparse.RequestParser()


class ErrorType(object):
    OK = {'code': 200, 'msg': "OK"}

    wrong_password = {'code': 20001, 'msg': "密码错误"}
    user_does_not = {'code': 20002, 'msg': "用户不存在"}

    column_name_not = {'code': 30001, 'msg': "栏目名称重复"}

    SESSION_ID_IS_EXPIRED = {'code': 30001, 'msg': "登录状态过期"}

    SIGNATURE_IS_EXPIRED = {'code': 10001, 'msg': "签名失效"}
    SIGNATURE_PARAMS_ERROR = {'code': 10002, 'msg': "参数异常"}
    SIGNATURE_IS_ERROR = {'code': 10003, 'msg': "签名错误"}
    EXISTS_INFO = {'code': 10004, 'msg': "信息已存在"}


class WtreesApi(Api):
    def handle_error(self, e):
        if isinstance(e, WtreesError):
            # current_app.sqlalchemy.db.session.rollback()
            return WtreesReturn.error(e.msg)
        return super(WtreesApi, self).handle_error(e)


class WtreesError(HTTPException):

    def __init__(self, msg, errors=[]):
        super(WtreesError, self).__init__()

        if isinstance(msg, dict):
            self.msg = msg
        else:
            self.msg = {'code': 40001, 'msg': msg}
            if errors:
                self.msg["data"] = errors

    def dump_msg(self):
        return self.msg["msg"]


class WtreesReturn(object):

    @staticmethod
    def dump(dict_obj):
        return json.dumps(dict_obj, cls=AlchemyEncoder)

    @staticmethod
    def error(error_type, data={}):
        """
            转换接口返回输出
        """
        # 获取只需要的字段， 当存在allow_fields时，则移除 allow_fields 中以外的字段
        data['ret'] = error_type
        # current_app.logger.debug("return result data:{}".format(data))
        resp = make_response(json.dumps(data, cls=AlchemyEncoder))
        resp.headers["content-type"] = 'application/json'
        return resp

    @staticmethod
    def data(error_type, data={}, page={}):
        """
            转换接口返回输出
        """
        tmp = {'ret': error_type}
        if data:
            tmp['data'] = data
        if page:
            tmp['page_info'] = page
        resp = json.dumps(tmp, cls=AlchemyEncoder)
        # resp.headers["content-type"] = 'application/json'
        resp = json.loads(resp)
        return resp

    @staticmethod
    def response(error_type, data={}, page={}):
        """
            转换接口返回输出
        """
        data = json.dumps(data, cls=AlchemyEncoder)
        data = json.loads(data)
        data['ret'] = error_type
        if page:
            data['page'] = page
        return data

    @staticmethod
    def load(json_str):
        return json.loads(json_str)


def random_word(length):
    return ''.join(random.choice(string.lowercase) for i in range(length))


from sqlalchemy.ext.declarative import DeclarativeMeta


class AlchemyEncoder(json.JSONEncoder):
    need_attr = []  # 如果为空dict则为全部
    not_need_attr = []

    def default(self, obj):
        if isinstance(obj, bytes):
            return str(obj, encoding='utf-8')
        if isinstance(obj, datetime.datetime):
            return obj.strftime("%Y-%m-%d %H:%M:%S")
        if isinstance(obj, datetime.date):
            return obj.strftime("%Y-%m-%d")
        if isinstance(obj, decimal.Decimal):
            return float(decimal.Decimal(obj).quantize(decimal.Decimal('0.00')))
        if isinstance(obj.__class__, DeclarativeMeta):
            # an SQLAlchemy class
            fields = {}
            for field in [x for x in obj.__dict__ if not x.startswith('_') and x != 'metadata']:
                if field == "query_class" or field == "query":
                    continue
                if hasattr(obj, "filter_attr") and field in obj.filter_attr():
                    continue
                if len(AlchemyEncoder.need_attr) > 0 and field not in AlchemyEncoder.need_attr:
                    continue
                if len(AlchemyEncoder.not_need_attr) > 0 and field in AlchemyEncoder.not_need_attr:
                    continue
                data = obj.__getattribute__(field)
                # print field, type(data)
                try:
                    json.dumps(data)  # this will fail on non-encodable values, like other classes
                    fields[field] = data
                except TypeError:  # 添加了对datetime的处理
                    if isinstance(data, datetime.datetime):
                        fields[field] = data.strftime("%Y-%m-%d %H:%M:%S")
                    elif isinstance(data, datetime.date):
                        fields[field] = data.isoformat()
                    elif isinstance(data, datetime.timedelta):
                        fields[field] = (datetime.datetime.min + data).time().isoformat()
                    elif isinstance(data, decimal.Decimal):
                        fields[field] = float(str(data))
                    elif isinstance(data, dict):
                        fields[field] = json.loads(json.dumps(data, cls=AlchemyEncoder))
                    else:
                        fields[field] = None
            # a json-encode able dict
            return fields

        return json.JSONEncoder.default(self, obj)


def get_json_wrapper(cls):
    def _call(cls_inst, inst_need_attr=None, not_need_attr=None, is_dict=False, other_attrs=None):
        if inst_need_attr is None:
            inst_need_attr = [c.key for c in class_mapper(cls_inst.__class__).column_attrs]

        if not_need_attr is None:
            not_need_attr = []

        if other_attrs:
            inst_need_attr.extend(other_attrs)

        AlchemyEncoder.need_attr = inst_need_attr
        AlchemyEncoder.not_need_attr = not_need_attr

        if is_dict:
            return json.loads(json.dumps(cls_inst, cls=AlchemyEncoder))
        return json.dumps(cls_inst, cls=AlchemyEncoder)

    cls.__call__ = _call
    return cls


def choice_type_fix(cls):
    """
        修改使用ChoiceType时，直接返回值
    :param cls:
    :return:
    """

    def _getattribute(self, *args, **kwargs):
        obj = object.__getattribute__(self, *args, **kwargs)
        if isinstance(obj, Choice):
            return obj.code
        return obj

    cls.__getattribute__ = _getattribute
    return cls
